Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjust the way nullable annotations are represented in metadata #31212

Merged
merged 7 commits into from
Nov 19, 2018

Conversation

AlekseyTs
Copy link
Contributor

Closes #30075.
Closes #30065.
Closes #29594.

@AlekseyTs AlekseyTs requested review from a team as code owners November 16, 2018 00:49
@jcouv jcouv self-assigned this Nov 16, 2018
@jcouv jcouv added this to the 16.0.P2 milestone Nov 16, 2018
@AlekseyTs
Copy link
Contributor Author

@cston, @jcouv Please review.

@jasonmalinowski jasonmalinowski changed the base branch from dev16.0-preview2 to master November 16, 2018 19:39
@@ -22,54 +22,43 @@ namespace System.Runtime.CompilerServices
AllowMultiple = false)]
public sealed class NullableAttribute : Attribute
Copy link
Member

@jcouv jcouv Nov 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NullableAttribute [](start = 24, length = 17)

Not blocking this PR: we'll need to think about how the values of the parameters will be stored in the fields of NullableAttribute. Jared mentioned we need to store those values so that they are accessible from reflection.

Tracked by #30143 #Resolved


NullableAttribute(1) should be placed on a type parameter definition that has a `class!` constraint.
NullableAttribute(2) should be placed on a type parameter definition that has a `class?` constraint.
Other forms of NullableAttribute ar not emitted on type parameter definitions and are not specially recognized on them.
Copy link
Member

@jcouv jcouv Nov 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ar [](start = 33, length = 2)

typo: are #Resolved

string OptString; // string?
[Nullable(new[] { true, false, true })]
[Nullable(new[] { 2, 1, 2 })]
Dictionary<string, object> OptDictionaryOptValues; // Dictionary<string!, object?>?
Copy link
Member

@jcouv jcouv Nov 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

object> [](start = 19, length = 7)

Should this be Dictionary<..., object?>? in source as well? Never mind. The type here is from PE. #Closed

{
AttributeInfo info = FindTargetAttribute(token, AttributeDescription.NullableAttribute);
Debug.Assert(!info.HasValue || info.SignatureIndex == 0 || info.SignatureIndex == 1);

defaultTransform = 0;
Copy link
Member

@jcouv jcouv Nov 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0; [](start = 31, length = 2)

Consider using another value to represent "not a value" and only set to 0 at line 2437 (from TryExtractValueFromAttribute). #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using another value to represent "not a value" and only set to 0 at line 2437 (from TryExtractValueFromAttribute).

I see no benefit in inventing new "defaults" when this value just works, this is a well established pattern for functions that return information via out parameters and return boolean value indicating success of the operation. Consumers that really depend on the fact whether the attribute was found and cracked successfully should simply check return value of this method.


In reply to: 234368109 [](ancestors = 234368109)

@@ -8,7 +8,7 @@ In source, nullable reference types are annotated with `?`.
string? OptString; // may be null
Dictionary<string, object?>? OptDictionaryOptValues; // dictionary may be null, values may be null
```
A warning is reported when annotating a reference type or unconstrained generic type with `?` outside a `NonNullTypes(true)` context.
A warning is reported when annotating a reference type or unconstrained generic type with `?` outside a `#nullable` context.
Copy link
Member

@gafter gafter Nov 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it was an error to annotate an unconstrained generic type. #Resolved

break;

case NullableAnnotation.Unknown:
Debug.Assert((NullableAnnotation)transformFlag == NullableAnnotation.Unknown);
Copy link
Member

@cston cston Nov 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug.Assert((NullableAnnotation)transformFlag == NullableAnnotation.Unknown); [](start = 20, length = 78)

The assert looks redundant. #Closed

@cston
Copy link
Member

cston commented Nov 17, 2018

            }

These checks are repeated many times. Please consider extracting a helper method: CheckReferenceTypeConstraint(t11, expectedValue: false);.


Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:41929 in 60ce669. [](commit_id = 60ce669, deletion_comment = False)


case NullableAnnotation.Unknown:
Debug.Assert((NullableAnnotation)transformFlag == NullableAnnotation.Unknown);
if (result.NullableAnnotation != NullableAnnotation.Unknown && (!result.NullableAnnotation.IsAnyNullable() || !oldTypeSymbol.IsNullableType()))
Copy link
Member

@jcouv jcouv Nov 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(!result.NullableAnnotation.IsAnyNullable() || !oldTypeSymbol.IsNullableType()) [](start = 83, length = 79)

nit: took me a while to understand this check. Inverting this last block helps: .. && !(result.NullableAnnotation.IsAnyNullable() && oldTypeSymbol.IsNullableType())
Maybe add a comment too: "// keep nullable annotation on Nullable<T> types" #Resolved

var type = comp.GetWellKnownType(WellKnownType.Microsoft_CodeAnalysis_EmbeddedAttribute);
Assert.True(type.IsErrorType());

// https://github.com/dotnet/roslyn/issues/29683 CSharpCompilation.AbstractSymbolSearcher needs to inject namespaces and types too
Copy link
Member

@jcouv jcouv Nov 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#29683 [](start = 15, length = 45)

can be closed? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be closed?

I think it can be, I'll add to the list.


In reply to: 234738966 [](ancestors = 234738966)

@jcouv
Copy link
Member

jcouv commented Nov 19, 2018

    public void ExplicitAttribute_MissingParameterlessConstructor()

This test requires both constructors (because we have simple and mixed case of nullability). I assume we produce the same error even if we only need one constructor and it exists. #Resolved


Refers to: src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Nullable.cs:74 in 60ce669. [](commit_id = 60ce669, deletion_comment = False)

@AlekseyTs
Copy link
Contributor Author

    public void ExplicitAttribute_MissingParameterlessConstructor()

I assume we produce the same error even if we only need one constructor and it exists.

Yes, this change didn't change the number of constructors and didn't change the way we complain about one of them is missing.


In reply to: 440002524 [](ancestors = 440002524)


Refers to: src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Nullable.cs:74 in 60ce669. [](commit_id = 60ce669, deletion_comment = False)

@@ -98,7 +98,7 @@ private void AddNullableAnnotations(TypeSymbolWithAnnotations typeOpt)
}

if (format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier) &&
!typeOpt.IsNullableType() &&
!typeOpt.IsNullableType() && !typeOpt.IsValueType &&
Copy link
Member

@jcouv jcouv Nov 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!typeOpt.IsValueType && [](start = 45, length = 23)

I didn't understand what's a scenario were we put a nullable annotation on a value type, but it's not Nullable<T>.
Is the when loading bad metadata? #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't understand what's a scenario were we put a nullable annotation on a value type, but it's not Nullable.
Is the when loading bad metadata?

On the contrary, binding from source does that, always did.


In reply to: 234741474 [](ancestors = 234741474)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From our discussion, some of the weird combinations (int marked as nullable) only come from PE.
You mentioned that one reason for letting such combinations into the compiler is to avoid cycles. Do you remember what's a scenario?
In particular, I wonder why is that problem unique to nullability, but was ok for decoding dynamic or tuple names?

If it were possible, I still think it would be better for metadata import to ignore, rather than to propagate, bad metadata combinations. I'd expect all value types (except Nullable<T>) should be imported as oblivious. I believe that is what existing metadata import tends to do.

Along those lines, why encode a nullable annotation for value types at all? We don't encode a dynamic flag for types that are not object, and we don't encode tuple names for types that are not tuples.


In reply to: 234742698 [](ancestors = 234742698,234741474)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mentioned that one reason for letting such combinations into the compiler is to avoid cycles. Do you remember what's a scenario?

For example, when we are loading type parameter constraints, we cannot ask questions whether the type parameter is a value type or a reference type

In particular, I wonder why is that problem unique to nullability, but was ok for decoding dynamic or tuple names?

In order to decode dynamic or tuple names we do not check whether targets are value or reference types either.

If it were possible, I still think it would be better for metadata import to ignore, rather than to propagate, bad metadata combinations. I'd expect all value types (except Nullable) should be imported as oblivious. I believe that is what existing metadata import tends to do.

I do not think this is something we should pursue. In any case, I think this PR doesn't change the way we import metadata in that specific regard, and it wasn't one of the goals to change this.

Along those lines, why encode a nullable annotation for value types at all? We don't encode a dynamic flag for types that are not object, and we don't encode tuple names for types that are not tuples.

Compiler always marks value types as oblivious in metadata, but we should be prepared to deal with malformed cases. This particular line of code makes sure that display doesn't confuse the reader that it deals with Nullable<T> case.


In reply to: 234772025 [](ancestors = 234772025,234742698,234741474)

@jcouv
Copy link
Member

jcouv commented Nov 19, 2018

    [Fact]

Not related to a file in this PR: since we're undoing the injection of NNT attribute, two EnC tests can be re-enabled. #29662
Considering re-enabling along with this change #Resolved


Refers to: src/Workspaces/CoreTest/SymbolKeyTests.cs:68 in 60ce669. [](commit_id = 60ce669, deletion_comment = False)

@@ -206,9 +206,8 @@ private ImmutableArray<TypeSymbolWithAnnotations> GetDeclaredConstraintTypes()
}
}

// https://github.com/dotnet/roslyn/issues/30075: Test different [NonNullTypes] on method and containing type.
Copy link
Member

@jcouv jcouv Nov 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#30075 [](start = 27, length = 45)

Can be resolved? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be resolved?

#30075 is already listed as addressed by this change.


In reply to: 234747704 [](ancestors = 234747704)

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM Thanks (iteration 4)

Other forms of NullableAttribute ar not emitted on type parameter definitions and are not specially recognized on them.

The `NullableAttribute` type declaration is synthesized by the compiler if it is not included in the compilation, but is needed to produce the output.

```c#
// C# representation of metadata
Copy link
Member

@jcouv jcouv Nov 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to expand this section with an example involving value types (int and Nullable<int>). #Resolved

@AlekseyTs AlekseyTs merged commit 19b50a4 into dotnet:master Nov 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants